#include <linux/compat.h>
#include <linux/delay.h>
#include <linux/file.h>
#include <linux/idr.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/slab.h>

#include <linux/cdev.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/completion.h>
#include <linux/kthread.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/poll.h>
#include <linux/wait.h>
#include <linux/eventpoll.h>

#include <linux/version.h>

#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/cpu.h>
#include <linux/kasan.h>
#include <linux/percpu.h>

#include <asm/cpu.h>
#include <asm/cpufeature.h>


struct test_dev {
	const char *name;
	const char *drv_name;
	const char *api_ver;
	int status;
	struct device *pdev;

	u32 dev_id;
	unsigned long dev_state;
	struct cdev cdev;
	struct device dev;
	void *priv;
};

static struct class *test_class;
static DEFINE_IDR(test_idr);
static dev_t test_devt;

#define TEST_DEV_FROM_DEV_ATTR(dev) container_of(dev, struct test_dev, dev)

static ssize_t id_show(struct device *dev, struct device_attribute *attr,
		       char *buf)
{
	struct test_dev *testdev = TEST_DEV_FROM_DEV_ATTR(dev);

	return sprintf(buf, "%d\n", testdev->dev_id);
}
static DEVICE_ATTR_RO(id);

static ssize_t api_ver_show(struct device *dev, struct device_attribute *attr,
			    char *buf)
{
	struct test_dev *testdev = TEST_DEV_FROM_DEV_ATTR(dev);

	return sprintf(buf, "%s\n", testdev->api_ver);
}
static DEVICE_ATTR_RO(api_ver);

static ssize_t dev_state_show(struct device *dev, struct device_attribute *attr,
			      char *buf)
{
	// struct test_dev *testdev = TEST_DEV_FROM_DEV_ATTR(dev);
	struct test_dev *testdev = dev_get_drvdata(dev);
	return sprintf(buf, "dev_state %lu\n", testdev->dev_state);
}

static ssize_t dev_state_store(struct device *dev,
			       struct device_attribute *attr, const char *buf,
			       size_t count)
{
	// struct test_dev *testdev = TEST_DEV_FROM_DEV_ATTR(dev);
	struct test_dev *testdev = dev_get_drvdata(dev);
	unsigned long val = 0;

	pr_info("dev_state store buf: %s, count %lu\r\n", buf, count);
	if (kstrtoul(buf, 0, &val) < 0)
		return -EINVAL;

	testdev->dev_state = val;

	return count;
}

static DEVICE_ATTR_RW(dev_state);

static struct attribute *test_dev_attrs[] = {
	&dev_attr_id.attr,
	&dev_attr_dev_state.attr,
	&dev_attr_api_ver.attr,
	NULL,
};

static const struct attribute_group test_dev_attr_group = {
	.name = "attrs",
	.attrs = test_dev_attrs,
};

static const struct attribute_group *test_dev_attr_groups[] = {
	&test_dev_attr_group, NULL
};

static void tasklet_work(struct tasklet_struct *task)
{
	pr_info("call %s task %p\n", __func__, task);
}

DECLARE_TASKLET(aaa, tasklet_work);

static void test_work_handler(struct work_struct *w)
{
	pr_info("call %s work %p\n", __func__, w);
}

struct work_struct bbb;

static int test_fops_open(struct inode *inode, struct file *filep)
{
	pr_info("call %s\r\n", __func__);
	schedule_work(&bbb);

	tasklet_schedule(&aaa);

	return 0;
}
static int test_fops_release(struct inode *inode, struct file *filep)
{
	pr_info("call %s\r\n", __func__);
	return 0;
}

static long test_fops_unl_ioctl(struct file *filep, unsigned int cmd,
				unsigned long arg)
{
	pr_info("call %s\r\n", __func__);
	return 0;
}

#ifdef CONFIG_COMPAT
static long test_fops_compat_ioctl(struct file *filep, unsigned int cmd,
				   unsigned long arg)
{
	pr_info("call %s\r\n", __func__);
	arg = (unsigned long)compat_ptr(arg);
	return test_fops_unl_ioctl(filep, cmd, arg);
}
#endif

static int test_fops_mmap(struct file *filep, struct vm_area_struct *vma)
{
	pr_info("call %s\r\n", __func__);
	return -1;
}

static const struct file_operations testdev_fops = {
	.owner = THIS_MODULE,
	.open = test_fops_open,
	.release = test_fops_release,
	.unlocked_ioctl = test_fops_unl_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = test_fops_compat_ioctl,
#endif
	.mmap = test_fops_mmap,
	// .poll = testdev_fops_poll,
};
static void test_dev_release(struct device *dev)
{
}

static int test_create_chrdev(struct test_dev *testdev)
{
	int ret;

	ret = idr_alloc(&test_idr, testdev, 0, 0, GFP_KERNEL);
	if (ret < 0)
		return ret;

	cdev_init(&testdev->cdev, &testdev_fops);
	testdev->dev_id = ret;
	testdev->cdev.owner = THIS_MODULE;

	device_initialize(&testdev->dev);
	testdev->dev.devt = MKDEV(MAJOR(test_devt), testdev->dev_id);
	testdev->dev.class = test_class;
	testdev->dev.groups = test_dev_attr_groups;
	// testdev->dev.parent = testdev->pdev;
	testdev->dev.release = test_dev_release;
	// dev_set_name(&testdev->dev, "%s-%d", testdev->name, testdev->dev_id);
	// dev_set_name(&testdev->dev, "%s", testdev->name);
	dev_set_drvdata(&testdev->dev, testdev);
	ret = cdev_device_add(&testdev->cdev, &testdev->dev);
	if (ret)
		goto err_with_idr;

	// device_create_file(&testdev->dev, &dev_attr_dev_state);
	dev_dbg(&testdev->dev, "create testdev minior=%d\n", testdev->dev_id);
	return 0;

err_with_idr:
	idr_remove(&test_idr, testdev->dev_id);
	return ret;
}

static void test_destroy_chrdev(struct test_dev *testdev)
{
	// device_remove_file(&testdev->dev, &dev_attr_dev_state);
	cdev_device_del(&testdev->cdev, &testdev->dev);
	put_device(&testdev->dev);
	memset(&testdev->dev, 0, sizeof(struct device));
	idr_remove(&test_idr, testdev->dev_id);
}

static bool is_phytium_e2000_soc(void)
{
	static bool first = true;
	static bool is_e2000q = false;
	int cpu;

	if (first == false) 
		return is_e2000q;
		
	for_each_cpu(cpu, cpu_possible_mask) {
		struct cpuinfo_arm64 *cpuinfo = &per_cpu(cpu_data, cpu);
		u32 midr = cpuinfo->reg_midr;
		/* No phytium soc */
		if (MIDR_IMPLEMENTOR(midr) != 0x70) {
			break;
		}

		if (MIDR_PARTNUM(midr) == 0x664) {
			is_e2000q = true;
			break;
		}
	}

	first = false;
	
	pr_info("Is e2000 %d\n", is_e2000q);
	
	return is_e2000q;
}

struct test_dev *global_testdev;
static int __init chrdev_init(void)
{
	int ret;
	
	if (is_phytium_e2000_soc())
		pr_info("=====\n");
	else 
		pr_info("xxxxx\n");


	INIT_WORK(&bbb, test_work_handler);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)

	test_class = class_create("test");
#else
	test_class = class_create(THIS_MODULE, "test");
#endif

	if (IS_ERR(test_class)) {
		ret = PTR_ERR(test_class);
		goto err;
	}
	pr_debug("call %s\r\n", __func__);
	pr_info("call %s\r\n", __func__);
	ret = alloc_chrdev_region(&test_devt, 0, MINORMASK, "mychar");
	if (ret)
		goto err_with_class;

	global_testdev = kmalloc(sizeof(struct test_dev), GFP_KERNEL);
	if (!global_testdev) {
		goto err_with_class;
	}

	global_testdev->name = "testdev";
	global_testdev->drv_name = "testdev_drv";
	global_testdev->api_ver = "0.0.1";

	ret = test_create_chrdev(global_testdev);
	if (ret) {
		kfree(global_testdev);
		global_testdev = NULL;
		goto err_with_class;
	}

	pr_info("Init with major number:%d\n", MAJOR(test_devt));

	return 0;

err_with_class:
	class_destroy(test_class);
err:
	return ret;
}

static void __exit chrdev_exit(void)
{
	pr_info("call %s\r\n", __func__);
	pr_debug("call %s\r\n", __func__);
	if (global_testdev) {
		test_destroy_chrdev(global_testdev);
		kfree(global_testdev);
	}

	unregister_chrdev_region(test_devt, MINORMASK);
	class_destroy(test_class);
	idr_destroy(&test_idr);
}

module_init(chrdev_init);
module_exit(chrdev_exit);

MODULE_DESCRIPTION("Module named chrdev.");
MODULE_LICENSE("GPL");
